﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;

using AutoMapper;

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Agile
{
    public class AgileEngine : IEngine
    {
        #region Properties
        private IServiceProvider _serviceProvider { get; set; }
        public virtual IServiceProvider ServiceProvider => _serviceProvider;
        public IConfiguration Configuration { get; internal set; }
        #endregion

        #region Utilities
        protected IServiceProvider GetServiceProvider()
        {
            var accessor = ServiceProvider.GetService<IHttpContextAccessor>();
            var context = accessor.HttpContext;
            return context?.RequestServices ?? ServiceProvider;
        }
        protected virtual void RunStartupTasks(ITypeFinder typeFinder)
        {
            var startupTasks = typeFinder.FindClassesOfType<IStartupTask>();
            var instances = startupTasks
                .Select(startupTask => (IStartupTask)Activator.CreateInstance(startupTask))
                .OrderBy(startupTask => startupTask.Order);
            foreach (var task in instances)
                task.Execute();
        }
        private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            try
            {
                var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
                if (assembly != null)
                    return assembly;
                var tf = Resolve<ITypeFinder>();
                assembly = tf.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name);
                return assembly;
            }
            catch (Exception)
            {

                throw;
            }
           
        }
        #endregion

        #region Methods
        public IServiceProvider ConfigureServices(IServiceCollection services, IConfiguration configuration)
        {
            Configuration = configuration;

            var typeFinder = new WebAppTypeFinder();

            var startupConfigurations = typeFinder.FindClassesOfType<IAgileStartup>();
            var instances = startupConfigurations
                .Select(startup => (IAgileStartup)Activator.CreateInstance(startup))
                .OrderBy(startup => startup.Order);

            foreach (var instance in instances)
                instance.ConfigureServices(services, configuration);

            //run startup tasks
            RunStartupTasks(typeFinder);

            //register engine
            services.AddSingleton<IEngine>(this);
            //register type finder
            services.AddSingleton<ITypeFinder>(typeFinder);

            //resolve assemblies here. otherwise, plugins can throw an exception when rendering views
            AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;

            _serviceProvider = services.BuildServiceProvider();
            return _serviceProvider;
        }
        public void ConfigureRequestPipeline(IApplicationBuilder application)
        {
            var typeFinder = Resolve<ITypeFinder>();
            var startupConfigurations = typeFinder.FindClassesOfType<IAgileStartup>();
            var instances = startupConfigurations
                .Select(startup => (IAgileStartup)Activator.CreateInstance(startup))
                .OrderBy(startup => startup.Order);

            foreach (var instance in instances)
                instance.Configure(application);
        }

        public T Resolve<T>() where T : class
        {
            return (T)Resolve(typeof(T));
        }
        public object Resolve(Type type)
        {
            return GetServiceProvider().GetService(type);
        }
        public virtual IEnumerable<T> ResolveAll<T>()
        {
            return (IEnumerable<T>)GetServiceProvider().GetServices(typeof(T));
        }
        public virtual object ResolveUnregistered(Type type)
        {
            Exception innerException = null;
            foreach (var constructor in type.GetConstructors())
            {
                try
                {
                    //try to resolve constructor parameters
                    var parameters = constructor.GetParameters().Select(parameter =>
                    {
                        var service = Resolve(parameter.ParameterType);
                        if (service == null)
                            throw new Exception("Unknown dependency");
                        return service;
                    });

                    //all is ok, so create instance
                    return Activator.CreateInstance(type, parameters.ToArray());
                }
                catch (Exception ex)
                {
                    innerException = ex;
                }
            }
            throw new Exception("No constructor was found that had all the dependencies satisfied.", innerException);
        }

        public virtual string GetConfig(string key)
        {
            return Configuration[key];
        }
        public virtual T GetConfig<T>(string key)
        {
            return Configuration.GetValue<T>(key);
        }
        #endregion

        #region License
        private static LicenseContext? _licenseType = null;
        internal static bool _licenseSet = false;
        public static LicenseContext? LicenseContext
        {
            get
            {
                return _licenseType;
            }
            set
            {
                _licenseType = value;
                _licenseSet = _licenseType.HasValue;
            }
        }
        internal static bool IsLicenseSet()
        {
            if (_licenseSet)
            {
                return true;
            }
            if (!Debugger.IsAttached)
            {
                _licenseSet = true;
                return true;
            }
            string text = Environment.GetEnvironmentVariable("AgileLicenseContext");
            bool flag;
            if (string.IsNullOrEmpty(text))
            {
                text = ((IConfiguration)new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json", true, false).Build())["AgileEngine:LicenseContext"];
                flag = false;
            }
            else
            {
                flag = true;
            }
            if (string.IsNullOrEmpty(text))
            {
                flag = false;
                return false;
            }
            text = text.Trim().ToLower();
            if (text.Equals("commercial"))
            {
                LicenseContext = Agile.LicenseContext.NonCommercial;
                _licenseSet = true;
                return _licenseSet;
            }
            if (text.Equals("noncommercial"))
            {
                LicenseContext = Agile.LicenseContext.NonCommercial;
                _licenseSet = true;
                return _licenseSet;
            }
            if (flag)
            {
                throw new Exception("LicenseContext is set to an invalid value in the environment variable 'AgileLicenseContext'. Please use Commercial or Noncommercial");
            }
            throw new Exception("LicenseContext is set to an invalid value in the configuration file, Key: AgileEngine.LicenseContext. Please use Commercial or Noncommercial");
        }
        #endregion

    }
}